#!/usr/bin/env python
# encoding: utf-8
"""
decode_iphone_backup.py

Decodes MobileSync/Backup/*.mdbackup files into a normal filesystem.

*** 1. I strongly recommend running this off a COPY of the backup folder rather than your live one. ***
*** 2. I am not responsible for anything bad that happens as a result of using this code ***
*** 3. If you don't understand the above, don't use the script.

Created by: IVS
Create Date: Monday 9th July 2007
Contact: chat.with.ivs[@t]gmail.com

Thanks to JavaCoderEx, CentroniX, Lixivial for beta testing !    

July 08: 	Updated to fix problems with newer version of iTunes / iPhone
August 08:	Updated to handle spaces in filenames.  
			Note: 	The new Third Party Apps files don't seem to be stored in the same folder hierarchy as on the phone.
				    This could be fixed by cross referencing the file names with the decoded Manifest.plist, but I don't 
					have time right now.
"""

import base64, os, commands, sys, getopt
from sgmllib import SGMLParser

def usage():
	print "MobileSync Backup Folder Decoder\n"
	print "Usage: " + sys.argv[0] + " (List of .mdbackup files to convert.  Wildcards are supported) \n"
	print "Built by ivs"

class bplist_converter:
	def decode_bplist(self, plist_filename):
		decode_command = "plutil -convert xml1 \"%(plist_filename)s\"" % locals()
		status=os.system(decode_command)
		if not status == 0:
			print "Error converting from binary plist using plutil."
			sys.exit(2)

class data_section:
	def __init__(self):
		self.data = []
		self.path = ""
	def decode(self):
		return base64.b64decode("".join(self.data))
	def write(self):
		self.path = os.path.join("MobileSyncExport",self.path)
		thepath, thefile = os.path.split(self.path)       
		
		#if the folders don't exists, make 'em
		if not os.path.exists(thepath):
			os.makedirs(thepath) 
		
		# convert from base64
		if (sys.version[:1] > (2,4)):	
			output_text = base64.b64decode("".join(self.data))
		else: 
			output_text = base64.decodestring("".join(self.data))		

		if output_text == "":
			print "No text to output."
			print "path " + self.path
			return
		
		print "the file:" + thefile	 
				
		output_file = open(self.path, 'wb') 
			
		output_file.write(output_text)
		output_file.close()
		
		# check here if it's a plist and decode it using plutil
		if output_text[0:6] == "bplist":
			c = bplist_converter()
			c.decode_bplist(self.path)
		
		
class plist_processor(SGMLParser):
	def reset(self):
		SGMLParser.reset(self)
		self.inkey = 0
		self.indata = 0
		self.sections = []
		self.currentkey = ""
		self.currentdata=data_section()
		
	def start_key(self, attrs): 
		self.inkey = 1
		
	def end_key(self):        
		self.inkey = 0
		
	def start_string(self, attrs):
		self.start_data(attrs)
	def end_string(self):
		self.end_data()
	
	def start_data(self, attrs):
		self.indata = 1	
	def end_data(self):
		self.indata = 0
		
	def unknown_starttag(self, tag, attrs):
		#strattrs = "".join([' %s="%s"' % (key, value) for key, value in attrs])
		#self.handle_data("<%(tag)s%(strattrs)s>" % locals())
		return None

	def unknown_endtag(self, tag):
		#self.handle_data("</%(tag)s>" % locals())
		return None
    
	def do_false(self, tag): 
		return None          
		
	def process_key_path(self, text):
		self.currentdata.path = text
	
	def process_key_data(self, text):
		self.currentdata.data.append(text)

	def process_key_string(self,text):
		self.process_key_data(text)	   
	
	def process_key_greylist(self,text):       
		#print "Inkey: " + str(self.inkey) + " key: " + str(self.currentkey)
		self.currentdata.path = text
		return None
	                            
	def process_key_version(self, text):
		# We don't need to do anything with the version key.
		return None

	def handle_data(self, text):
		# called for each block of plain text, i.e. outside of any tag and
		# not containing any character or entity references
		# Store the original text verbatim.             
		if self.inkey == 1:
			self.currentkey = text.lower()	
			print "In key: %(text)s" % locals()
		elif self.indata == 1:
			try: 
				key_function = getattr(self,"process_key_%s" % self.currentkey )
				key_function(text)
			except AttributeError:
				print "Warning: No function exists to handle key: %s.  It will be ignored." % self.currentkey

	def write(self):
		self.currentdata.write()
	def output(self):
		print "Path: %s \n" % self.currentdata.path
		print "Data: %s \n" % self.currentdata.decode()

def main(argv):
	
	try:
		opts, args = getopt.getopt(argv[1:], "",[])
	except getopt.GetoptError:
		usage()
		sys.exit(2)
	
	if args.__len__() == 0:
		usage()
		sys.exit(2)
	
	converter = bplist_converter()
	
	for filename in args:
		print "Processing: " + filename
		parser = plist_processor()
		plist_name = filename
		plist_file = open(filename, 'r')
		plist_text = plist_file.read()
		if plist_text[0:6] == "bplist":
			plist_file.close()
			converter.decode_bplist(plist_name)
			plist_file = open(plist_name, 'r')
			plist_text = plist_file.read()
		parser.feed(plist_text)
		parser.write()

if __name__ == "__main__":
	main(sys.argv)